package rmi.drs.server.vancouver;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;

import rmi.drs.common.domain.Item;
import rmi.drs.common.interfaces.RemoteServer;
import rmi.drs.server.vancouver.VancouverServerImpl;
import rmi.drs.util.Lock;
import rmi.drs.util.MyDatagramSocket;

public class VancouverServerImpl extends UnicastRemoteObject implements
		RemoteServer {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	/**
	 * 
	 */
	public static final int UDPPORT = 9004;
	private static int buyCount = 0;
	private static int returnCount = 0;
	private static boolean visitedToBuy = false;
	private static List<Item> items = new ArrayList<Item>();
	private static Lock initialize = new Lock();
	private static Lock flush = new Lock();
	public static String registryName = "Vancouver";

	private static Registry registry;
	
	private static String data_folder = "data/";
	private static String filename = data_folder + "Vancouver.txt";
	
	private static VancouverServerImpl INSTANCE = null;
	
	private VancouverServerImpl(Registry reg) throws RemoteException{
		super();
		initiateItemsFromDatabase();
		registry = reg;
	}
	
	private static Item getItemById(String id){
		Item $return = null;
		for (Item i : items){
			if (i.getId().equalsIgnoreCase(id)){
				$return = i;
				break;
			}
		}
		return $return;
	}

	@Override
	public boolean buy(String customerId, String itemId, int numberOfItem)
			throws RemoteException {
		// TODO Auto-generated method stub
		itemId = itemId.toUpperCase();
		
		///lock items
		boolean $return = false;
		Item item = getItemById(itemId);
		if (item == null){
			String[] serversString = registry.list();
			for (int i=0; i<serversString.length; i++){
				if(!serversString[i].equalsIgnoreCase(registryName)){
					try {
						RemoteServer server = (RemoteServer) (registry.lookup(serversString[i]));
						if (!visitedToBuy){
							visitedToBuy = true;
							$return = server.buy(customerId, itemId, numberOfItem);
						}
						else {
							continue;
						}
					} catch (NotBoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						continue;
					}
				}
			}
		}
		
		else{
			if (!item.getLock().isLocked()){
				try {
					item.getLock().lock();
					int available = item.getNumberOfItems();
					int remaining = available - numberOfItem;
					this.updateCustomerFile(customerId, itemId, numberOfItem, "Bought on " + (new Date()).toString());
					item.setNumberOfItems(remaining);
					buyCount++;
					if(buyCount%3 == 0){
						flushToFile();
					}
					$return = true;
					item.getLock().unlock();
				}
				catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		return $return;
	}

	@Override
	public boolean returnItems(String customerId, String itemId,
			int numberOfItem) throws RemoteException {
		// TODO Auto-generated method stub
		itemId = itemId.toUpperCase();
		boolean $return = false;
		Item item = getItemById(itemId);
		if (item != null){
			if (!item.getLock().isLocked()){
				try {
					item.getLock().lock();
					
					int available = item.getNumberOfItems();
					int remaining = available + numberOfItem;
					this.updateCustomerFile(customerId, itemId, numberOfItem, "Returned on " + (new Date()).toString());
					item.setNumberOfItems(remaining);
					returnCount++;
					if(returnCount%3 == 0){
						flushToFile();
					}
					$return = true;
					item.getLock().unlock();
				}
				catch (Exception e){
					e.printStackTrace();
				}
			}
		}
		else {
			Item newItem = new Item(itemId, numberOfItem);
			items.add(newItem);
		}
		return $return;
		
	}
	
	public List<Item> getItems() throws RemoteException{
		return items;
	}
	
	@Override
	public void flushToFile() throws RemoteException{
		if (!flush.isLocked()){
			try {
				flush.lock();
				PrintWriter pw = null;
				try {
					pw = new PrintWriter(new FileOutputStream(filename));
					for (Item i: items){
						pw.println(i.getId() + " " + i.getNumberOfItems());
					}
					pw.flush();
				} catch (FileNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				flush.unlock();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public RemoteServer getServerByCustomerId(String userId)
			throws RemoteException {
		// TODO Auto-generated method stub
		char first = userId.charAt(0);
		String[] serversString = registry.list();
		RemoteServer server = this;
		for (int i=0; i<serversString.length; i++){
			if (serversString[i].charAt(0) == first){
				try {
					server = (RemoteServer) (registry.lookup(serversString[i]));
					break;
				} catch (NotBoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		return server;
	}

	@Override
	public boolean checkStock(String itemId) throws RemoteException {
		// TODO Auto-generated method stub
		itemId = itemId.toUpperCase();
		Item item = getItemById(itemId);
		boolean $return = false;
		if (item != null){
			System.out.println(item.getNumberOfItems() + " available in " + registryName + "." );
			$return = true;
		}
		return $return;
	}

	public void initiateItemsFromDatabase() throws RemoteException {
		// TODO Auto-generated method stub
		Scanner fl = null;
		
		try{
			if (!initialize.isLocked()){
				initialize.lock();
				items.clear();
				fl = new Scanner(new FileInputStream(filename));
				while(fl.hasNextLine()){
					String item = fl.nextLine();
					if (item != null){
						String[] itemsArr = item.split(" ");
						String id = itemsArr[0].trim();
						int number = Integer.parseInt(itemsArr[1]);
						Item newItem = new Item(id, number);
						items.add(newItem);
						
						System.out.print(itemsArr[0].trim() + Integer.parseInt(itemsArr[1]));
						//set locks for each item
					}
				}
				initialize.unlock();
			}
			
		}
		catch (Exception e){
			e.printStackTrace();
		}
		finally{
			fl.close();
			}
		System.out.println("\n\nItems loaded: ");
		for (Item i : items){
			System.out.println(i.getId() + ": " + i.getNumberOfItems());
		}
	}

	private void updateCustomerFile(String customerId, String itemId,
			int numberOfItem, String update) throws RemoteException {
		// TODO Auto-generated method stub
		String filename = data_folder + customerId;
		PrintWriter pw = null;
		try{
			pw = new PrintWriter(new FileOutputStream(filename, true));
			pw.println(itemId + " " + numberOfItem + " " + update);
				
		}
		catch (Exception e){
			e.printStackTrace();
		}
		finally{
			pw.flush();
			pw.close();
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		try {
			Registry reg = LocateRegistry.getRegistry(1099);
	        reg.rebind("Vancouver", getInstance(reg));
	        System.out.println("The vancouver server is ready");
	        MyDatagramSocket socket = new MyDatagramSocket(UDPPORT);
	        while (true){
	        	String message = socket.receiveMessage();
	        	String itemId = message.split("-")[1].trim();
	        	getInstance(reg).checkStock(itemId);
	        }
	        
		}
		catch (Exception e) {
            System.err.println("Something wrong happended on the remote end");
            e.printStackTrace();
        }
	}
	
	@Override
	public String getServerName() throws RemoteException {
		// TODO Auto-generated method stub
		return registryName;
	}

	public static VancouverServerImpl getInstance(Registry reg){
		if (INSTANCE == null){
			try {
				INSTANCE = new VancouverServerImpl(reg);
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return INSTANCE;
	}

}
